package mge
{
 import flash.display.Sprite;
 import flash.events.Event;
   /**
  * ...
  * @author ivan
  *
  *    Typical usage example:
  *
           onScreenChange():void
           {
               var foreground:Sprite = new Sprite; // This sprite is over everything else
               addChild(foreground);
               var st:ScreenTransition = new ScreenTransition(foreground, _view.width, _view.height);
               st.fadeToBlack(1000, fadeEnd);

               function fadeEnd():void
               {
                   // Configures new scene while screen is black
                   // ...

                   // Fades to transparent
                   st.fadeToBlack(1000, transitionComplete, true);
               }

               function transitionComplete():void
               {
                   removeChild(foreground);
               }
           }
  */
 public class ScreenTransition
 {
   // target: Sprite where to draw tarnsition
   // width, height: size of the rectangle drawed (usually size of application).
   public function ScreenTransition(target:Sprite, width:int, height:int)
   {
     this.target = target;
     this.width = width;
     this.height = height;
   }

   public static const EFFECT_ALPHA:int = 0;
   public static const EFFECT_DIAMONDS:int = 1;
   public static const EFFECT_HORZ_BARS:int = 2;
   public static const EFFECT_CIRCLES:int = 3;
   public static const EFFECT_BAR_CODE:int = 4;
   public static const EFFECT_TRIANGLE_TRAP:int = 5;
   public static const EFFECT_SQUARES:int = 6;
   public static const EFFECT_SQUARES_RANDOM:int = 7;
   public static const EFFECT_SQUARES_ORDERED:int = 8;
   public static const EFFECT_SQUARES_SPIRAL:int = 9;
   public static const EFFECT_CINEMATIC:int = 10;
   public static const NUM_EFFECTS:int = 11;

   // time_ms: time the fade effect lasts.
   // reversed: the transition can be to black or from black if it's reversed.
   // callback: function called when the effect it's done.
   // effect: effect type used.
   public function fadeToBlack(time_ms:int,
                               callback:Function = null,
                               reversed:Boolean = false,
                               effect:int = EFFECT_ALPHA):void
   {
       this.effect = effect;
       this.callback = callback;
       this.reversed = reversed;
       target.addEventListener(Event.ENTER_FRAME, onEnterFrame);
       timer = new Timer(time_ms);
       timer.start();

       initialized = false;
       lastFrameDraw = false;
   }

   private function onEnterFrame(e:Event):void
   {
       var delta:Number = timer.getDelta();

       var f:Function;

       switch(effect)
       {
           default:
           case EFFECT_ALPHA: f = alpha; break;
           case EFFECT_DIAMONDS: f = diamonds; break;
           case EFFECT_HORZ_BARS: f = horzBars; break;
           case EFFECT_CIRCLES: f = circles; break;
           case EFFECT_SQUARES: f = squares; break;
           case EFFECT_BAR_CODE: f = barCode; break;
           case EFFECT_TRIANGLE_TRAP: f = triangleTrap; break;
           case EFFECT_SQUARES_RANDOM: f = squaresRandom; break;
           case EFFECT_SQUARES_ORDERED: f = squaresOrdered; break;
           case EFFECT_SQUARES_SPIRAL: f = squareSpiral; break;
           case EFFECT_CINEMATIC: f = cinematic; break;
       }

       f(reversed ? 1 - delta : delta);

       if (delta >= 1)
       {
         if (lastFrameDraw)
         {
           target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
           if (callback != null) callback();
         }
         else
          lastFrameDraw = true;
       }
   }

   private function alpha(delta:Number):void
   {
       target.graphics.clear();
       target.graphics.beginFill(0x000000, delta);
       target.graphics.drawRect(0, 0, width, height);
       target.graphics.endFill();
   }

   private function diamonds(delta:Number):void
   {
       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       var columns:int = 40;
       var rows:int = 20;

       var x_sep:Number = width / columns;
       var y_sep:Number = height / rows;

       var v:Vector.<Number> = new Vector.<Number>();

       for (var i:int = 0; i < columns + 1; i++)
       for (var j:int = 0; j < rows + 1; j++)
       {
           var x:Number =  x_sep * i;
           var y:Number = y_sep * j;

           var scale_x:Number =  x_sep * delta;
           var scale_y:Number = y_sep * delta;

           // [This could be optional] (makes diamonds near border of screen grow faster)
           scale_x *= 1 + Math.abs(i - columns / 2) * 0.03;
           scale_y *= 1 + Math.abs(j - rows / 2) * 0.03;
           if (scale_x >  x_sep) scale_x =  x_sep;
           if (scale_y > y_sep) scale_y = y_sep;
           // [/This could be optional]

           v.push(x - scale_x);
           v.push(y);

           v.push(x);
           v.push(y - scale_y);

           v.push(x);
           v.push(y + scale_y);

           v.push(x);
           v.push(y - scale_y);

           v.push(x + scale_x);
           v.push(y);

           v.push(x);
           v.push(y + scale_y);
       }

       target.graphics.drawTriangles(v);
       target.graphics.endFill();
   }

   private function horzBars(delta:Number):void
   {
       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       var rows:int = 15;

       for (var i:int = 0; i < rows; i++)
       {
           var bar_height:Number = height / rows * delta;
           bar_height *= 1 + i / rows * 1.8;
           if (bar_height > height / rows) bar_height = height / rows;
           target.graphics.drawRect(0, height / rows * i, width, bar_height);
       }

       target.graphics.endFill();
   }

   private function circles(delta:Number):void
   {
       target.graphics.clear();

       var columns:int = 20;

       var sep:Number = width / columns;

       for (var i:int = 0; i < columns; i++)
       for (var j:int = 0; j < columns; j++)
       {
           target.graphics.beginFill(0x000000);

           var maxScale:Number = sep * Math.sqrt(2) / 2;

           var scale:Number = maxScale * delta;

           var xd:Number = Math.abs( i - columns / 2 );
           var yd:Number = Math.abs( j - columns / 2 );

           scale *= 1 + Math.sqrt(xd * xd + yd * yd) * 0.03;

           if (scale > maxScale) scale = maxScale;

           target.graphics.drawCircle(sep * i, sep * j, scale);
           target.graphics.endFill(); // This needs to be done every drawCircle to avoid glitches when circles overlap.
       }
   }

   private function squares(delta:Number):void
   {
       var size:Number = 40; // square size
       var x_num:Number = width / size;
       var y_num:Number = height / size;

       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       for (var j:int = 0; j < y_num; j++)
       for (var i:int = 0; i < x_num; i++)
       {
           var x:Number = i * size;
           var y:Number = j * size;

           var xd:Number = x - width / 2;
           var yd:Number = y - height / 2;
           var scale:Number = delta;
           scale *= 1 + Math.sqrt(xd * xd + yd * yd) * 0.03;
           if (scale > 1) scale = 1;
           target.graphics.drawRect(x, y, size * scale, size * scale);
       }

       target.graphics.endFill();
   }

   private var randLinesPositions:Vector.<int>;
   private function barCode(delta:Number):void
   {
       var lines:int = 60;

       // Initialization
       function comp(a:int, b:int):Number
       {
           if (a > b) return +1;
           if (a < b) return -1;
           return 0;
       }

       if (!initialized)
       {
           initialized = true;
           randLinesPositions = new Vector.<int>();
           randLinesPositions.push(0);
           randLinesPositions.push(width);
           while(randLinesPositions.length < lines)
               randLinesPositions.push(Math.random() * width);
           randLinesPositions.sort(comp);
       }

       // Actual update
       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       for (var i:int = 0; i < lines - 1; i++)
       {
           var w:Number = (randLinesPositions[i + 1] - randLinesPositions[i]) * delta;
           target.graphics.drawRect(randLinesPositions[i], 0, w, height);
       }

       target.graphics.endFill();
   }

   private function triangleTrap(delta:Number):void
   {
       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       var spikes:int = 10;

       var y_sep:Number = height / spikes;

       var v:Vector.<Number> = new Vector.<Number>();

       for (var j:int = -1; j < spikes + 1; j++)
       {
           var advance:Number = width * delta;
           advance *= 1 + j * 0.1;
           if (advance < width * delta) advance = width * delta;
           if (advance > width) advance = width;

           v.push(0);
           v.push(j * y_sep);

           v.push(advance);
           v.push(j * y_sep + y_sep / 2);

           v.push(0);
           v.push((j + 1) * y_sep);

           v.push(width);
           v.push(j * y_sep + y_sep / 2);

           v.push(width - advance);
           v.push(j * y_sep + y_sep);

           v.push(width);
           v.push((j + 1) * y_sep + y_sep / 2);
       }

       target.graphics.drawTriangles(v);
       target.graphics.endFill();
   }

   private function swap(a:int, b:int, v:Vector.<int>):void
   {
       var t:int = v[a];
       v[a] = v[b];
       v[b] = t;
   }

   private var squaresRandomIdx:Vector.<int>;
   private function squaresRandom(delta:Number):void
   {
       var size:Number = 10; // square size
       var x_num:int = width / size;
       var y_num:int = height / size;
       var total:int = x_num * y_num;

       if (!initialized)
       {
           squaresRandomIdx = new Vector.<int>();

           for (var i:int = 0; i < total; i++)
               squaresRandomIdx.push(i);

           for (i = 0; i < squaresRandomIdx.length; i++)
               swap( i, i + Math.random() * (squaresRandomIdx.length - i), squaresRandomIdx);

           initialized = true;
       }

       target.graphics.clear();


       for (i = 0; i < total; i++)
       {
           var index:int = squaresRandomIdx[i];

           var y:int = (int)(index / x_num) * size;
           var x:int = (int)(index % x_num) * size;
           var scale:Number = 1;

           var alpha:Number = delta;
           alpha *= 1 + i * 0.02;
           if (alpha > 1) alpha = 1;

           target.graphics.beginFill(0x000000, alpha);
           target.graphics.drawRect(x, y, size * scale, size * scale);
           target.graphics.endFill();
       }

   }

   private function squaresOrdered(delta:Number):void
   {
       var size:Number = 16; // square size
       var x_num:Number = width / size;
       var y_num:Number = height / size;

       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       for (var j:int = 0; j < y_num; j++)
       for (var i:int = 0; i < x_num; i++)
       {
           var x:Number;

           if ( ((int)(j) % 2) == 0 )
               x = size * i;
           else
               x = (width - size) - size * i;

           var y:Number = size * j;

           var idx:int = y_num * j + i;

           var scale:Number = delta;
           scale *= 1 + idx * 0.03;
           if (scale > 1) scale = 1;

           target.graphics.drawRect(x, y, size * scale, size * scale);
       }
       target.graphics.endFill();
   }

   private var squareSpiralIdx:Vector.<int>;
   private function squareSpiral(delta:Number):void
   {
       var size:Number = 10; // square size
       var x_num:int = width / size;
       var y_num:int = height / size;
       var total:int = x_num * y_num;

       if (!initialized)
       {
           squareSpiralIdx = new Vector.<int>();

           function spiralSquare(inner:int):Vector.<int>
           {
               var ans:Vector.<int> = new Vector.<int>();
               for (var i:int = inner; i < x_num - inner; i++)
                   ans.push(i + inner * x_num);
               for (i = inner + 1; i < y_num - inner - 1; i++)
                   ans.push(x_num - inner + (i * x_num - 1));
               for (i = x_num - inner - 1; i > inner; i--)
                   ans.push(x_num * (y_num - inner - 1) + i);
               for (i = y_num - inner - 1; i > inner; i--)
                   ans.push(inner + (i * x_num));

               return ans;
           }

           for (var i:int = 0; i < Math.min(x_num, y_num) / 2; i++)
           {
               var s:Vector.<int> = spiralSquare(i);
               for (var j:int = 0; j < s.length; j++)
                   squareSpiralIdx.push(s[j]);
           }

           for (i = squareSpiralIdx.length-1; i >= 0; i--)
           {
               if ( (i % 2) == 0)
               {
                   var t:int = squareSpiralIdx[i];
                   squareSpiralIdx.splice(i, 1);
                   squareSpiralIdx.push(t);
               }
           }

           initialized = true;
       }

       target.graphics.clear();
       target.graphics.beginFill(0x000000);

       for (i = 0; i < squareSpiralIdx.length * delta; i++)
       {
           var index:int = squareSpiralIdx[i];

           var y:int = (int)(index / x_num) * size;
           var x:int = (int)(index % x_num) * size;

           target.graphics.drawRect(x, y, size, size);

       }

       target.graphics.endFill();
   }
   
   private function cinematic(delta:Number):void
   {
     target.graphics.clear();
     if (delta == 0) return;
     
     target.graphics.beginFill(0x000000, delta);
     
     var ease:Ease = new Ease(Ease.bounceOut_01);
     
     var size:Number = 80; //  100 * ease.delta(delta, 1);
     target.graphics.drawRect(0, 0, width, size);
     
     target.graphics.drawRect(0, height - size, width, size);
     
     target.graphics.endFill();
   }


   private var timer:Timer;
   private var target:Sprite;
   private var effect:int = EFFECT_ALPHA;
   private var callback:Function;
   private var width:int;
   private var height:int;
   private var reversed:Boolean;
   private var initialized:Boolean;
   private var lastFrameDraw:Boolean;



 }

}